package me.seu.demo.test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * OCR 结果排序
 *
 * @author: liangfeihu
 * @since: 2025-10-23 10:42
 */


/**
 * 定义OCR Block对象
 */
class OcrBlock {
    int[] recBoxes; // [x1, y1, x2, y2]
    String recText;
    int textLength;

    public OcrBlock(int[] recBoxes, String recText, int textLength) {
        this.recBoxes = recBoxes;
        this.recText = recText;
        this.textLength = textLength;
    }

    public int getLeft() {
        return recBoxes[0];
    }

    public int getTop() {
        return recBoxes[1];
    }

    public int getRight() {
        return recBoxes[2];
    }

    public int getBottom() {
        return recBoxes[3];
    }

    public String getRecText() {
        return recText;
    }

    public int[] getRecBoxes() {
        return recBoxes;
    }

    public int getTextLength() {
        return textLength;
    }

    @Override
    public String toString() {
        return "OcrBlock{" +
                "recText='" + recText + '\'' +
                ", recBoxes=" + Arrays.toString(recBoxes) +
                ", textLength=" + textLength +
                '}';
    }
}

public class OcrSorter {

    /**
     * Y坐标比较的容差，用于判断是否在同一行/列
     * 可根据实际情况调整
     */
    private static final int Y_TOLERANCE = 15;

    /**
     * 对OCR结果进行排序和分块
     *
     * @param blocks OCR解析得到的原始块列表
     * @return 分块后的文本列表
     */
    public static List<String> sortAndClusterBlocks(List<OcrBlock> blocks) {
        if (blocks == null || blocks.isEmpty()) {
            return new ArrayList<>();
        }

        // 步骤1: 排序 - 先按顶部Y坐标（从上到下），再按左侧X坐标（从左到右）
        // 使用 getTop() 作为主要排序键，getLeft() 作为次要排序键
        // 为了处理Y坐标可能有轻微偏差的情况，可以将Y坐标按容差分组
        List<OcrBlock> sortedBlocks = blocks.stream()
                .sorted(Comparator.comparingInt((OcrBlock b) -> b.getTop() / Y_TOLERANCE * Y_TOLERANCE)
                        .thenComparingInt(OcrBlock::getLeft))
                .collect(Collectors.toList());

        // 步骤2: 分块 - 基于排序后的顺序，将相邻且可能属于同一逻辑块的文本合并
        List<List<OcrBlock>> clusteredGroups = new ArrayList<>();
        List<OcrBlock> currentGroup = new ArrayList<>();

        for (OcrBlock block : sortedBlocks) {
            if (currentGroup.isEmpty()) {
                currentGroup.add(block);
            } else {
                OcrBlock lastBlock = currentGroup.get(currentGroup.size() - 1);

                // 判断当前块是否应该加入到当前组
                // 简单策略：检查Y坐标范围是否有显著重叠或Y中心点距离
                boolean shouldCluster = shouldCluster(lastBlock, block);

                if (shouldCluster) {
                    currentGroup.add(block);
                } else {
                    // 当前组结束，开始新组
                    clusteredGroups.add(new ArrayList<>(currentGroup));
                    currentGroup = new ArrayList<>();
                    currentGroup.add(block);
                }
            }
        }
        // 添加最后一组
        if (!currentGroup.isEmpty()) {
            clusteredGroups.add(currentGroup);
        }

        // 将每个组的文本合并成一个字符串
        List<String> result = clusteredGroups.stream()
                .map(group -> group.stream()
                        .map(OcrBlock::getRecText)
                        .filter(text -> !text.trim().isEmpty()) // 过滤空字符串
                        .collect(Collectors.joining(" "))) // 使用空格连接，可根据需要调整
                .filter(text -> !text.isEmpty()) // 过滤合并后可能产生的空字符串
                .collect(Collectors.toList());

        return result;
    }

    /**
     * 判断两个Block是否应该聚类到一起
     * 这里使用一个简单的启发式规则：检查Y坐标范围是否有重叠或中心点距离
     *
     * @param block1 前一个块
     * @param block2 当前块
     * @return 是否应该聚类
     */
    private static boolean shouldCluster(OcrBlock block1, OcrBlock block2) {
        // 检查Y坐标范围是否有重叠
        boolean yOverlap = Math.max(block1.getTop(), block2.getTop()) <= Math.min(block1.getBottom(), block2.getBottom());

        // 检查Y坐标中心点距离是否在容差内
        int center1Y = (block1.getTop() + block1.getBottom()) / 2;
        int center2Y = (block2.getTop() + block2.getBottom()) / 2;
        boolean yClose = Math.abs(center1Y - center2Y) <= Y_TOLERANCE;

        // 检查X坐标范围是否接近或重叠（可选，用于处理多列或紧密排列的情况）
        // boolean xOverlap = Math.max(block1.getLeft(), block2.getLeft()) <= Math.min(block1.getRight(), block2.getRight());
        // boolean xClose = Math.abs(block1.getRight() - block2.getLeft()) <= X_TOLERANCE || Math.abs(block2.getRight() - block1.getLeft()) <= X_TOLERANCE;

        // 综合判断：Y坐标重叠或Y中心点接近，且X坐标不相距太远（防止将不同列的文本连在一起）
        // 这里可以调整逻辑，例如只用yClose，或者结合xClose
        // 假设页面布局相对规整，主要依赖yOverlap或yClose，同时确保X坐标不相距过远（例如，不是从一列的末尾跳到另一列的开头）
        // 粗略判断：如果Y中心点接近，且X坐标范围不完全分离（即不是A在B的左边且A的右边界小于B的左边界，或者A在B的右边且A的左边界大于B的右边界）
        boolean xNotCompletelySeparate = !(block1.getRight() < block2.getLeft() || block2.getRight() < block1.getLeft());

        return (yOverlap || yClose) && xNotCompletelySeparate;
    }

    public static void main(String[] args) {
        // 示例数据
        List<OcrBlock> inputBlocks = Arrays.asList(
                new OcrBlock(new int[]{72, 31, 865, 60}, "EGFR阳性NSCLC相毬野⽣型患者可能具有更⾼的远处转移风险", 31),
                new OcrBlock(new int[]{60, 89, 716, 112}, "• EGFR突变型与野⽣型NSCLC似乎具有相似的疾病复发率，5年复发率约为40%，", 42),
                new OcrBlock(new int[]{77, 122, 678, 143}, "但在复发人群中，EGFR阳性患者相毬野⽣型患者具有更⾼的远处转移风险。", 35),
                new OcrBlock(new int[]{136, 171, 410, 193}, "复发患者的远处转移风险显著升高", 15),
                new OcrBlock(new int[]{633, 171, 800, 193}, "脑转移风险显著升高", 9),
                new OcrBlock(new int[]{266, 217, 336, 234}, "P=0.007", 7),
                new OcrBlock(new int[]{701, 214, 767, 237}, "P<0.01", 6),
                new OcrBlock(new int[]{201, 247, 241, 266}, "97%", 3),
                new OcrBlock(new int[]{517, 241, 581, 260}, "20.0%", 5),
                new OcrBlock(new int[]{86, 258, 139, 277}, "100%", 4),
                new OcrBlock(new int[]{631, 255, 683, 274}, "15.9%", 5),
                new OcrBlock(new int[]{94, 282, 139, 304}, "80%", 3),
                new OcrBlock(new int[]{358, 283, 399, 303}, "68%", 3),
                new OcrBlock(new int[]{517, 278, 571, 298}, "15.0%", 5),
                new OcrBlock(new int[]{788, 283, 841, 302}, "12.2%", 5),
                new OcrBlock(new int[]{94, 309, 138, 330}, "60%", 3),
                new OcrBlock(new int[]{517, 314, 571, 333}, "10.0%", 5),
                new OcrBlock(new int[]{95, 335, 139, 357}, "40%", 3),
                new OcrBlock(new int[]{527, 351, 572, 371}, "5.0%", 4),
                new OcrBlock(new int[]{95, 362, 141, 382}, "20%", 3),
                new OcrBlock(new int[]{105, 388, 138, 408}, "0%", 2),
                new OcrBlock(new int[]{527, 388, 572, 407}, "0.0%", 4),
                new OcrBlock(new int[]{182, 409, 260, 427}, "EGFR阳性", 6),
                new OcrBlock(new int[]{352, 406, 404, 428}, "野生型", 3),
                new OcrBlock(new int[]{618, 409, 697, 428}, "EGFR阳性", 6),
                new OcrBlock(new int[]{787, 408, 841, 429}, "野生型", 3),
                new OcrBlock(new int[]{73, 453, 451, 463}, "一项单中心回顾性研究纳⼊282例早期或局部晚期肺腺癌患者，评估患者", 33),
                new OcrBlock(new int[]{514, 453, 895, 463}, "⽇本一项研究对⽇本癌症联合委员会注册数据库（一个手术切除癌症患者", 32),
                new OcrBlock(new int[]{73, 469, 443, 481}, "复发、复发分期、复发时间和无进展生存期（PFS）。研究显示复发患", 32),
                new OcrBlock(new int[]{511, 467, 603, 482}, "的全国性数据库；", 8),
                new OcrBlock(new int[]{653, 467, 883, 482}, "）进行了回顾性分析。5780名IA-IIA期患者", 24),
                new OcrBlock(new int[]{73, 485, 389, 497}, "者中EGFR阳性患者远处转移风险相毬野生型患者显著升高。", 28),
                new OcrBlock(new int[]{517, 485, 789, 497}, "（41.0%）接受了EGFR突变检测，并被纳入本研究。", 27),
                new OcrBlock(new int[]{10, 515, 267, 525}, "Carlos Galvez, et al. Oncotarget. 2020;11(21):1953-1960.", 57),
                new OcrBlock(new int[]{606, 469, 658, 481}, "n=18973", 7),
                new OcrBlock(new int[]{890, 491, 947, 514}, "泰瑞沙", 3)
        );

        List<String> result = sortAndClusterBlocks(inputBlocks);

        System.out.println("排序并分块后的结果:");
        for (int i = 0; i < result.size(); i++) {
            System.out.println((i + 1) + ". " + result.get(i));
        }
    }

}
